在Tomcat或其他中间件产品上,有时或突然跳出一个response has been committed的异常。网上也有很多解决这个问题的攻略。为什么很多,或许写答案的人也不知道为什么这个方法解决了这个问题,而是各种尝试之后, 终于解决了。(边何人初见月,江月何年初照人)
什么叫response has been committed? 数据库有已提交的概念,表示相关事务已完成,可能数据并没有写入数据磁盘,而是位于redo日志中,无论怎么,事务已成功,即使此时掉电,也不会丢失更新。已提交的事务还可以撤回吗?当然不可以。(更多可了解redo与undo的区别)。
而Tomcat对于Response的已提交也是一样,表示Response已成即定事实,撤回不了了。Tomcat在这个response有任意一个字符发送到网络时,会标识response已提交。为什么呢?数据已经发出去,再也无法中断/撤回他了。所谓覆水难收就是这个意思。
我们看response has been committed一般发生在什么时候:
1) Java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed sendRedirect
什么意思呢? 当我想放弃当前的响应内容,回复一个304的响应让客户端重新请求。但这里因为发送缓冲区不足,Tomcat已将原先准备好的一些数据发送出去了,当然响应头会被首先发出。Tomcat是告诉你:哥们,不好意思,状态为200的响应我已经发送出去了。
2)Cannot forward after response has been committed
同1)一样,你想抛弃你当前的响应(response),forward到一个新的sevlet去响应当前的请求,Tomcat告诉你:你的响应已经发出去了, 撤不回来了。
3) sendError等。
sendError意欲改变响应状态码。设想这时状态码已经在网络链路间跋山涉水,奔向客户端,打个电话让他回来吗?
明白了问题产生的原因,更容易寻找解决的方法。为什么Tomcat会在我们决定sendRedirect时已经将response的数据发出去? 除了代码可能作了flush(将数据写入网络)操作,也有可能发送缓冲区较小,Tomcat只能将原有的数据写出网络。一个治标的方法,就是将缓冲区调大。治本的方法当然是:写代码要有计划。不然何至覆水难收?
更多细节参考 Servlet4 规范-5.1 Buffering